#pragma once

#include <stx/core/fs.hpp>

inline namespace stx
{
/*
[Reflection for aggregates] from Andrzej's C++ blog
https://akrzemi1.wordpress.com/2020/10/01/reflection-for-aggregates/
*/

struct Init__ final
{
    template<IsNonVoid T>
    [[nodiscard]] operator T() const; // NOLINT
};

template<std::size_t I>
struct Tag__ : Tag__<I - 1>
{};

template<>
struct Tag__<0>
{};

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<15>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 15uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<14>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 14uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<13>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 13uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<12>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 12uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<11>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 11uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<10>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 10uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<9>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 9uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<8>)
    -> decltype(T{Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{},
                  Init__{}},
                0uz)
{
    return 8uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<7>) -> decltype(
    T{Init__{}, Init__{}, Init__{}, Init__{}, Init__{}, Init__{}, Init__{}},
    0uz)
{
    return 7uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<6>)
    -> decltype(T{Init__{}, Init__{}, Init__{}, Init__{}, Init__{}, Init__{}},
                0uz)
{
    return 6uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<5>)
    -> decltype(T{Init__{}, Init__{}, Init__{}, Init__{}, Init__{}}, 0uz)
{
    return 5uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<4>)
    -> decltype(T{Init__{}, Init__{}, Init__{}, Init__{}}, 0uz)
{
    return 4uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<3>)
    -> decltype(T{Init__{}, Init__{}, Init__{}}, 0uz)
{
    return 3uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<2>)
    -> decltype(T{Init__{}, Init__{}}, 0uz)
{
    return 2uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<1>)
    -> decltype(T{Init__{}}, 0uz)
{
    return 1uz;
}

template<IsNonVoid T>
[[nodiscard]] constexpr auto AggregateFieldCount__(Tag__<0>)
    -> decltype(T{}, 0uz)
{
    return 0uz;
}

template<IsNonVoid T>
requires EXPR(std::is_aggregate_v<NoCvr<T>>)
[[nodiscard]] constexpr std::size_t AggregateFieldCount()
{
    return AggregateFieldCount__<NoCvr<T>>(Tag__<15>{});
}

template<IsNonVoid T>
requires EXPR(std::is_aggregate_v<NoCvr<T>>)
void visitAllFields(IsNonVoid auto fn, T&& obj)
{
    if constexpr (AggregateFieldCount<T>() == 15uz)
    {
        auto&& [m1,
                m2,
                m3,
                m4,
                m5,
                m6,
                m7,
                m8,
                m9,
                m10,
                m11,
                m12,
                m13,
                m14,
                m15] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
        std::invoke(fn, m10);
        std::invoke(fn, m11);
        std::invoke(fn, m12);
        std::invoke(fn, m13);
        std::invoke(fn, m14);
        std::invoke(fn, m15);
    }
    else if constexpr (AggregateFieldCount<T>() == 14uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14] =
            obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
        std::invoke(fn, m10);
        std::invoke(fn, m11);
        std::invoke(fn, m12);
        std::invoke(fn, m13);
        std::invoke(fn, m14);
    }
    else if constexpr (AggregateFieldCount<T>() == 13uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
        std::invoke(fn, m10);
        std::invoke(fn, m11);
        std::invoke(fn, m12);
        std::invoke(fn, m13);
    }
    else if constexpr (AggregateFieldCount<T>() == 12uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
        std::invoke(fn, m10);
        std::invoke(fn, m11);
        std::invoke(fn, m12);
    }
    else if constexpr (AggregateFieldCount<T>() == 11uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
        std::invoke(fn, m10);
        std::invoke(fn, m11);
    }
    else if constexpr (AggregateFieldCount<T>() == 10uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
        std::invoke(fn, m10);
    }
    else if constexpr (AggregateFieldCount<T>() == 9uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8, m9] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
        std::invoke(fn, m9);
    }
    else if constexpr (AggregateFieldCount<T>() == 8uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7, m8] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
        std::invoke(fn, m8);
    }
    else if constexpr (AggregateFieldCount<T>() == 7uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6, m7] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
        std::invoke(fn, m7);
    }
    else if constexpr (AggregateFieldCount<T>() == 6uz)
    {
        auto&& [m1, m2, m3, m4, m5, m6] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
        std::invoke(fn, m6);
    }
    else if constexpr (AggregateFieldCount<T>() == 5uz)
    {
        auto&& [m1, m2, m3, m4, m5] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
        std::invoke(fn, m5);
    }
    else if constexpr (AggregateFieldCount<T>() == 4uz)
    {
        auto&& [m1, m2, m3, m4] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
        std::invoke(fn, m4);
    }
    else if constexpr (AggregateFieldCount<T>() == 3uz)
    {
        auto&& [m1, m2, m3] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
        std::invoke(fn, m3);
    }
    else if constexpr (AggregateFieldCount<T>() == 2uz)
    {
        auto&& [m1, m2] = obj;
        std::invoke(fn, m1);
        std::invoke(fn, m2);
    }
    else if constexpr (AggregateFieldCount<T>() == 1uz)
    {
        auto&& [m1] = obj;
        std::invoke(fn, m1);
    }
}

using ProgramOptionName = std::string;
using ProgramOptionArg  = std::string;
using ProgramArg        = std::string;
using OptionsWithArg    = Map<ProgramOptionName, ProgramOptionArg>;
using OptionsWithOptArg = Map<ProgramOptionName, ProgramOptionArg>;
using OptionsWithNoArg  = Set<ProgramOptionName>;
using ProgramArgs       = std::deque<ProgramArg>;

struct ProgramCmdLines final
{
    OptionsWithArg    opts_with_arg;
    OptionsWithOptArg opts_with_opt_arg;
    OptionsWithNoArg  opts_with_no_arg;
    ProgramArgs       program_args;

    [[nodiscard]] bool empty() const;
};

class ProgramArgParser final
{
    using OptionNames_ = Set<ProgramOptionName>;

    OptionNames_ names_of_opt_with_opt_arg_;
    OptionNames_ names_of_opt_with_arg_;
    OptionNames_ names_of_opt_with_no_arg_;

public:
    ProgramArgParser& addShortOptions(std::string_view const sv_short_opts);
    ProgramArgParser&
    addLongOptionWithRequiredArg(std::string_view const sv_opt_name);
    ProgramArgParser&
    addLongOptionWithOptionalArg(std::string_view const sv_opt_name);
    ProgramArgParser&
    addLongOptionWithNoArg(std::string_view const sv_opt_name);
    [[nodiscard]] ProgramCmdLines parse(int const         argc,
                                        char const* const argv[]);

private:
    void checkDuplicateOptionNames_() const;
};
} // namespace stx

/*
Short Options:
--------------
Only [a-zA-Z0-9] are allowed for short options.

"xy:z;"
-------
-x: no_arg
-y: required_arg
-z: optional_arg

exe -y     // error. y has no argument.
exe -x     // ok.
exe -x xxx // ok. xxx is the non-option argument.
exe -yxxx  // ok. y's argument is xxx.
exe -y xxx // ok. y's argument is xxx.
exe -y=xxx // ok. y's argument is xxx.

exe -z=xxx // ok. z's argument is xxx.
exe -z xxx // ok. z's argument is null and the non-option argument is xxx.

exe -z=xxx -x // ok
exe -z xxx -x // ok. xxx is a program argument.

Long Options:
-------------
      no_arg: --foo
required_arg: --bar
optional_arg: --opt

exe --foo xxx // ok. xxx is a non-option argument.
exe --bar xxx // ok. xxx is --bar's argument.
exe --bar=xxx // ok. xxx is --bar's argument.
exe --bar     // error. --bar has no argument.
exe --opt     // ok. --opt has no argument.
exe --opt xxx // ok. xxx is a non-option argument. --opt has no argument.
exe --opt=xxx // ok. xxx --opt's argument.

exe --opt=xxx --bar yyy // ok
exe --opt xxx --bar yyy // error

More Long Options:
------------------------
 no_arg: --foo
has_arg: --bar

exe --foo xxx             // ok. xxx is a non-option argument.
exe --bar xxx             // ok. xxx is --bar's argument.
exe --bar 'xxx "yyy" zzz' // ok. "xxx \"yyy\" zzz" is --bar's argument.
exe --bar "xxx 'yyy' zzz" // ok. "xxx 'yyy' zzz" is --bar's argument.
exe --bar=xxx             // ok. xxx is --bar's argument.
exe --bar='xxx "yyy" zzz' // ok. 'xxx "yyy" zzz' is --bar's argument.
exe --bar="xxx 'yyy' zzz" // ok. "xxx 'yyy' zzz" is --bar's argument.
exe --bar                 // error. --bar has no argument.
*/